package com.dalxx.oclioscope;

import java.io.*;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Queue;
import java.util.ResourceBundle;
import java.util.TooManyListenersException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Semaphore;

import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.*;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.*;
import jfx.messagebox.MessageBox;
import gnu.io.*;

public class SubScene implements Initializable, SerialPortEventListener, Closeable {
	private boolean init=true;
	private double start=0;
	private BufferedOutputStream writer=null;;
	private BufferedInputStream reader=null;
	private SerialPort port=null;
	private boolean isOpened=false;
	private AnimationTimer timer=new AnimationTimer() {
        @Override public void handle(long now) {
            processData();
            
        }
    };
	
	
	//private Semaphore available=new Semaphore(1);
    
	
	private int nCh;
	//TiemSpan millisecond
	private int timeSpan=1000;
	private byte[] Data=new byte[12];
	/** Last Command  
	 * 'S' : Start
	 * 'X' : Stop
	 * 'C' : Configuration 
	 * */
	private int cmd=0;
	private Queue<Double> ChData[]=new Queue[8]; 
	private Queue<Double> ChTime;
	private double lastChTime=0;
	private boolean isProcessing=false;
	private boolean isTrashing=false;
	
	private Thread worker=null;
	private Queue<String> messages=new  ArrayBlockingQueue<String>(200);
	
	
	protected long buildUInt(byte[] src,int start)
	{
		int count=4;
		byte byteBuf[]=new byte[]{0,0,0,0,0,0,0,0};
		for(int i=0;i<count;i++)
		{
			byteBuf[i]=src[start+i];
		}
		ByteBuffer buf=ByteBuffer.wrap(byteBuf);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		return buf.getLong();
		
	}
	protected int buildUbyte(byte[] src,int start)
	{
		int count=1;
		byte byteBuf[]=new byte[]{0,0,0,0};
		for(int i=0;i<count;i++)
		{
			byteBuf[i]=src[start+i];
		}
		ByteBuffer buf=ByteBuffer.wrap(byteBuf);
		buf.order(ByteOrder.LITTLE_ENDIAN);
		return buf.getInt();
	}
	
	protected void processData() {
		// TODO Auto-generated method stub
		try {
			while(true)
			{
				String Out="";
				boolean s=false;
				while (this.reader.available()==0)
				{
					synchronized(this.reader)
					{
						this.reader.wait(100);
					}
					if(!this.isOpened) return;						
					
				}
					
					
				
				int ch=this.reader.read();
				System.out.print(ch+"/");
				switch(ch)
				{
				case 'D':
					
					int dataSize=4+this.nCh;
					while(this.reader.available()<dataSize);
					this.reader.read(this.Data, 0, dataSize);
					
					
					double time=(double)this.buildUInt(this.Data, 0)*1e-3%(double)this.timeSpan;
					
					for(byte x:this.Data)
					{
						System.out.print(x+"/");
					}
					
					
					System.out.print("  1:"+time+"/");					
								
					
					
					if(time<this.lastChTime)
					{
						if(this.isProcessing)
						{
							this.isTrashing=true;
						}
						else
						{
							this.isTrashing=false;
							//int size=this.ChTime.size();
							Platform.runLater(new Runnable(){
								int nloop=ChTime.size();
								@Override
								public void run() {
									isProcessing=true;
									// TODO Auto-generated method stub
									for(int i=0;i<nCh;i++)
										Chart.getData().get(i).getData().clear();
									for(int i=0;i<nloop;i++)
									{
										double t=ChTime.poll();
										for(int ch=0;ch<nCh;ch++)
										{									
											
											Chart.getData().get(ch).getData().add(
													new XYChart.Data<Double,Double>(t, ChData[ch].poll()));
										}
									}
									isProcessing=false;
								}
							}
								
							);
						}
					}
					else
					{
						if(this.isTrashing) 
							continue;
					}
					synchronized(this.ChTime){					
						if(!this.ChTime.offer(time))
							continue;
						for(int i=4;i<dataSize;i++)
						{
							double dataCh=(double)this.buildUbyte(this.Data, i)*5D/255D;
							this.ChData[i-4].offer(dataCh);
							System.out.print(i-4+":"+dataCh+"/");
						}
					}
					
					this.lastChTime=time;
					
					System.out.print("\n");
					break;				
				case 'S':					
					Out="Success\n";
					synchronized(this.messages){
						this.messages.offer(Out);	
					}
					this.cmd=0;
					break;
				case 'E':
					if(this.cmd=='C')
					{
						while(this.reader.available()<2);
						this.reader.read(this.Data, 0, 2);
						Out="Error:"+this.Data[0]+"/nCh="+this.Data[1]+"\n";
						synchronized(this.messages){
							this.messages.offer(Out);	
						}						
					}
					else
					{
						while(this.reader.available()<1);
						this.reader.read();
						Out="Error No Command\n";
						synchronized(this.messages){
							this.messages.offer(Out);	
						}
					}
					this.cmd=0;
					break;
				case 'C':
					Out="blocking\n";
					synchronized(this.messages){
						this.messages.offer(Out);
					}
					break;
				case 'T':
					Out="Timeout .\n";
					synchronized(this.messages){
						this.messages.offer(Out);
					}
				default:
					Out="Error not defined response.\n";
					synchronized(this.messages){
						this.messages.offer(Out);	
					}
				}
				Platform.runLater(new Runnable(){
					
					@Override
					public void run() {
						synchronized(messages)
						{
							String out=messages.poll();
							
							if(out != null)
								OutText.appendText(out);
						}
					}			
					
				});
			
					
				
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void initialize(URL location, ResourceBundle resources) {
		// TODO Auto-generated method stub
		this.init();
	}
	
	@SuppressWarnings("unchecked")
	public void init()	
	{
		this.Chart.setCreateSymbols(false);
		this.ChoiceCh=new ChoiceBox[]{
	    		this.ChoiceCh1, this.ChoiceCh2, this.ChoiceCh3, this.ChoiceCh4,
	    		this.ChoiceCh5
	    };
		setChanel(this.ChoiceNumCh.getItems());
		this.ChoiceNumCh.getItems().remove(0);
		this.ChoiceNumCh.getSelectionModel().selectFirst();
		this.ChoiceNumCh.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>(){

			@Override
			public void changed(ObservableValue<? extends Number> observable,
					Number oldValue, Number newValue) {
				for(int i=0;i<ChoiceCh.length;i++)
				{
					if(i<=newValue.intValue())
					{
						ChoiceCh[i].disableProperty().set(false);
						ChoiceCh[i].getSelectionModel().selectFirst();
					}else
					{
						ChoiceCh[i].disableProperty().set(true);
						ChoiceCh[i].getSelectionModel().selectFirst();
					}
				}
				
			}
			
		});
		for(ChoiceBox<String> ch:this.ChoiceCh)
		{
			setChanel(ch.getItems());
			ch.getSelectionModel().selectFirst();
			ch.disableProperty().set(true);
			
		}
		this.ChoiceCh1.disableProperty().set(false);
		
		
		
		//chart property
		
		//this.ChoiceCh6.getSelectionModel().selectFirst();
		//this.ChoiceCh7.getSelectionModel().selectFirst();
		//this.ChoiceCh8.getSelectionModel().selectFirst();
	}
	
	private void setChanel(ObservableList<String> list)
	{		
		
		list.add("0");
		list.add("1");
		list.add("2");
		list.add("3");
		list.add("4");
		list.add("5");
		list.add("6");
		list.add("7");
		list.add("8");
	}
	XYChart.Series<Double, Double> series = new XYChart.Series<Double, Double>();
	@FXML
	public LineChart<Double,Double> Chart;
	@FXML
	public ComboBox<String> ComboCom;
	@FXML
	public Button OpenButton;
	@FXML
	public Button CloseButton;
	@FXML
	public Button StartButton;
	@FXML
	public Button StopButton;
	@FXML
	public Button ConfigButton;
	@FXML
	public TextArea OutText;
	@FXML
	public ChoiceBox<String> ChoiceNumCh;
	@FXML
	public ChoiceBox<String> ChoiceCh1;
	@FXML
	public ChoiceBox<String> ChoiceCh2;
	@FXML
	public ChoiceBox<String> ChoiceCh3;
	@FXML
	public ChoiceBox<String> ChoiceCh4;
	@FXML
	public ChoiceBox<String> ChoiceCh5;
	
	
	private ChoiceBox<String> ChoiceCh[];

	@FXML 
	protected void ButtonAction(ActionEvent event) {
		    Chart.setTitle("Stock Monitoring, 2010");
	        //defining a series
		    if (Chart.getData().size()==0)
		    Chart.getData().clear();
	        
	        
	        series.setName("My portfolio");
	        //populating the series with data
	        series.getData().add(new XYChart.Data<Double,Double>(start+1.5D, 23D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+2.3D, 14.32D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+3D, 15D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+4D, 24D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+5D, 34D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+6D, 36D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+7D, 22D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+8D, 45D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+9D, 43D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+10D, 17D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+11D, 29D));
	        series.getData().add(new XYChart.Data<Double,Double>(start+12D, 25D));
	        start=start+12;
	        if(init)
	        {
	        	init=false;
	        	Chart.getData().add(series);
	        }
	}
	@FXML
	protected void ButtonDelete(ActionEvent event) {
		this.series.getData().remove(0, 12);
	
	
	
	}
	@FXML
	protected void onComboCom(ActionEvent event)
	{
		String o=this.ComboCom.getValue();
		if(o!=null)
		{
			OpenButton.disableProperty().set(false);
			MessageBox.show(null, "Open Button", "Alert", MessageBox.OK);
		}
		else 
		{
			OpenButton.disableProperty().set(true);
		}
	}
	@FXML
	protected void onScan(ActionEvent event) {
		
		EnumIterable<?> It= new EnumIterable<Object>(CommPortIdentifier.getPortIdentifiers());
		
		for (Object o :It)
		{
			CommPortIdentifier port=(CommPortIdentifier)o;
			this.ComboCom.getItems().add(port.getName());
		}
	}
	@FXML
	protected void onOpen(ActionEvent event) {
		try {
			CommPortIdentifier portId=CommPortIdentifier.getPortIdentifier(this.ComboCom.getValue());
			if(portId.getPortType()!=CommPortIdentifier.PORT_SERIAL)
				return;
			port=(SerialPort)portId.open("Ociloscope",20);
			port.addEventListener(this);
			port.notifyOnDataAvailable(true);
			port.setSerialPortParams(115200, SerialPort.DATABITS_8,
					SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
			
			this.reader=new BufferedInputStream(port.getInputStream());
			this.writer=new BufferedOutputStream(port.getOutputStream());
			this.isOpened=true;
			Runnable process=new Runnable()
			{

				@Override
				public void run() {
					processData();				
				}			
				
			};
			
			this.worker=new Thread(process);
			this.worker.start();			
			this.CloseButton.disableProperty().set(false);
			
			this.StopButton.disableProperty().set(false);
			this.ConfigButton.disableProperty().set(false);
			
		} catch (NoSuchPortException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (PortInUseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TooManyListenersException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedCommOperationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
				
	}
	
	
	@FXML
	protected void onClose(ActionEvent event) {
		this.CloseButton.disableProperty().set(true);
		this.StopButton.disableProperty().set(true);
		this.StartButton.disableProperty().set(true);
		this.ConfigButton.disableProperty().set(true);
		try {
			this.isOpened=false;
			if (this.worker.isAlive())			
				this.worker.join();
			this.worker=null;
				
			
			this.writer.close();			
			this.reader.close();
			this.port.removeEventListener();
			port.getInputStream().close();
			port.getOutputStream().close();
			
			//Thread.sleep(100);
			this.port.close();
			
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} //catch (InterruptedException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
 catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		this.timer.stop();
		this.port=null;
		this.writer=null;
		//this.reader=null;
	}

	@FXML
	protected void onStart(ActionEvent event) {
		try {
			this.cmd='S';
			this.writer.write('S');
			this.writer.flush();			
			
			
			//this.timer.start();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}
	
	@FXML
	protected void onStop(ActionEvent event) {
		try {
			this.cmd='X';
			this.writer.write('X');
			this.writer.flush();

			//Thread.sleep(100);
//			this.reader.read();
//			int ch=this.reader.read();
//			if(ch=='S')
//				this.OutText.appendText("Success\n");
//			else
//				this.OutText.appendText("Error\n");
			
//			this.timer.stop();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
//		 catch (InterruptedException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
	}
	
	@FXML
	protected void onConfig(ActionEvent event) {
		try {
			
			
			this.Chart.getData().clear();
			this.nCh=Integer.parseInt( ChoiceNumCh.getSelectionModel().getSelectedItem());
			//String str=this.SendText.getText();
			byte b[]=new byte[nCh+2];
			b[0]='C';
			b[1]=(byte)this.nCh;
			this.ChTime=new ArrayBlockingQueue<Double>(this.timeSpan*5);
			for(int i=0;i<nCh;i++)
			{
				b[i+2]=(byte)  ChoiceCh[i].getSelectionModel().getSelectedIndex();
				Series<Double,Double> ser=new Series<Double,Double>();
				ser.setName("Ch#"+ChoiceCh[i].getSelectionModel().getSelectedItem());
				this.Chart.getData().add(ser);
				ser.nodeProperty().get().setStyle("-fx-stroke-width: 1px;");
				this.ChData[i]=new ArrayBlockingQueue<Double>(this.timeSpan*5);
			}
			this.cmd='C';
			this.writer.write(b);
			this.writer.flush();
			
			this.StartButton.disableProperty().set(false);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}
	@Override
	public void serialEvent(SerialPortEvent event) {
		switch(event.getEventType()) {
		case SerialPortEvent.OUTPUT_BUFFER_EMPTY: 
			break;
		case SerialPortEvent.DATA_AVAILABLE:
			try{
				synchronized(this.reader)
				{
					this.reader.notify();
				}
			}catch(IllegalMonitorStateException e)
			{
				e.printStackTrace();
				System.out.print(e.getMessage()+"\n");
			}
			break;
		case SerialPortEvent.BI:		 
		case SerialPortEvent.CD:		 
		case SerialPortEvent.CTS:		 
		case SerialPortEvent.DSR:		 
		case SerialPortEvent.FE:		 
		case SerialPortEvent.OE:		 
		case SerialPortEvent.PE:
		case SerialPortEvent.RI:		
			break;
		default:
			break;
				
		 
		}
		
	}

	@Override
	public void close() throws IOException {
		// TODO Auto-generated method stub
		if(isOpened)
		{
			this.port.removeEventListener();
			port.getInputStream().close();
			port.getOutputStream().close();
			
			//Thread.sleep(100);
			this.port.close();
			this.isOpened=false;
		}
	}
	
}
